#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
-------------------------------------------------
   File Name：      redis.py
   Remark :         redis 操作工具类
-------------------------------------------------
"""


from __future__ import annotations

import time
import os
from functools import wraps

import redis
import logging

# from apitesting.config import settings

# 模块公开接口
__all__ = ["RedisHandler", "redis_handler"]

# Configure logging
logging.basicConfig(level=logging.NOTSET)
logger = logging.getLogger(__name__)


def measure_time(func):
    """
    装饰器，用于统计 Redis 操作的执行时间。
    """

    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.perf_counter()
        result = func(*args, **kwargs)
        end_time = time.perf_counter()
        elapsed_time = end_time - start_time
        logger.info(f"Executed {func.__name__} in {elapsed_time:.4f} seconds")
        return result

    return wrapper


class RedisHandler:
    def __init__(self,
                 host: str = None,
                 port: int = None,
                 password: str = None,
                 db: int = 0,
                 retry_on_failure: bool = True,
                 max_retries: int = 3,
                 max_connections: int | None = 10
                 ) -> None:
        """
        初始化 Redis 连接。
        :param host: Redis 主机地址
        :param port: Redis 端口
        :param password: Redis 密码
        :param db: Redis 数据库索引
        :param retry_on_failure: 是否在连接失败时重试
        :param max_retries: 最大重试次数
        :param max_connections: 最大连接数
        """
        self.host = host or os.getenv("REDIS_HOST", "localhost")
        self.port = port or int(os.getenv("REDIS_PORT", 6379))
        self.password = password or os.getenv("REDIS_PASSWORD")
        self.db = db
        self.retry_on_failure = retry_on_failure
        self.max_retries = max_retries
        self.max_connections = max_connections
        self.client = self._connect_to_redis()

    def _connect_to_redis(self):
        """
        建立 Redis 连接，支持连接池。
        """
        try:
            connection_pool = redis.ConnectionPool(
                host=self.host,
                port=self.port,
                password=self.password,
                db=self.db,
                decode_responses=True,
                max_connections=self.max_connections  # 配置最大连接数
            )
            client = redis.StrictRedis(connection_pool=connection_pool)
            client.ping()
            logger.info(f"Connected to Redis at {self.host}:{self.port} with connection pool")
            return client
        except redis.ConnectionError as e:
            logger.error(f"Failed to connect to Redis: {e}")
            if self.retry_on_failure:
                for attempt in range(self.max_retries):
                    time.sleep(2 ** attempt)  # 指数退避
                    try:
                        client = redis.StrictRedis(connection_pool=connection_pool)
                        client.ping()
                        logger.info(f"Connected to Redis at {self.host}:{self.port} after {attempt + 1} retries")
                        return client
                    except redis.ConnectionError:
                        continue
            raise

    def check_connection(self):
        """验证与 Redis 的连接"""
        try:
            # 尝试执行简单的 ping 操作来检查连接
            self.client.ping()
            logger.info("Redis connection is successful.")
        except redis.ConnectionError as e:
            logger.error(f"Redis connection failed: {e}")
            raise

    @staticmethod
    def handle_retry(func):
        """
        自动重试装饰器，用于封装 Redis 操作。
        """

        @wraps(func)
        def wrapper(self, *args, **kwargs):
            for attempt in range(self.max_retries):
                try:
                    return func(self, *args, **kwargs)
                except redis.ConnectionError as e:
                    logger.error(f"Redis operation failed: {e}. Retrying {attempt + 1}/{self.max_retries}...")
                    time.sleep(2 ** attempt)  # 指数退避
                except Exception as e:
                    logger.exception(f"Unexpected error during Redis operation: {e}")
                    break
            raise RuntimeError(f"Operation '{func.__name__}' failed after {self.max_retries} retries")

        return wrapper

    @handle_retry
    def scan_iter(self, match="*", count=1000):
        """
        使用 scan_iter 迭代获取 Redis 键，支持大规模键扫描。
        :param match: 匹配模式
        :param count: 每次返回的键数量
        :return: 键的生成器
        """
        return self.client.scan_iter(match=match, count=count)

    @handle_retry
    def set_key(self, key, value, ttl=None):
        """
        设置 Redis 键值对。
        :param key: 键
        :param value: 值
        :param ttl: 过期时间（秒）
        """
        self.client.set(key, value)
        if ttl:
            self.client.expire(key, ttl)
        logger.info(f"Set key '{key}' with value '{value}' and TTL {ttl}")

    @handle_retry
    def get_key(self, key):
        """
        获取 Redis 键的值。
        :param key: 键
        :return: 键的值
        """
        value = self.client.get(key)
        logger.info(f"Retrieved key '{key}' with value: {value}")
        return value

    @handle_retry
    def get_keys(self, pattern="*"):
        """
        根据模式获取 Redis 键。
        :param pattern: 键模式
        :return: 键列表
        """
        keys = self.client.keys(pattern)
        logger.info(f"Retrieved keys with pattern '{pattern}': {keys}")
        return keys

    @handle_retry
    def delete_key(self, key):
        """
        删除 Redis 键。
        :param key: 键
        """
        result = self.client.delete(key)
        if result:
            logger.info(f"Deleted key '{key}'")
        else:
            logger.warning(f"Key '{key}' does not exist")

    @handle_retry
    def execute_lua_script(self, script, keys=None, args=None):
        """
        执行 Lua 脚本。
        :param script: Lua 脚本
        :param keys: 脚本涉及的键
        :param args: 脚本的参数
        :return: 脚本执行结果
        """
        keys = keys or []
        args = args or []
        result = self.client.eval(script, len(keys), *(keys + args))
        logger.info(f"Executed Lua script '{script}' with result: {result}")
        return result

    @handle_retry
    def monitor_memory(self):
        """
        获取 Redis 内存信息。
        :return: 内存信息
        """
        memory_info = self.client.info("memory")
        logger.info(f"Memory info: {memory_info}")
        return memory_info

    @measure_time
    def get_prefix(self, prefix: str) -> list:
        """
        获取 Redis 中符合前缀的数据。
        :param prefix: 键前缀
        :return: 符合前缀的键值对
        """
        keys = list(self.scan_iter(match=f'{prefix}*'))
        values = self.client.mget(keys)
        result = [{key: value} for key, value in zip(keys, values) if value]
        logger.info(f"Retrieved {len(result)} items with prefix '{prefix}'")
        return result

    @measure_time
    def delete_prefix(self, prefix: str, exclude: str | None = None) -> None:
        """
        删除 Redis 中符合前缀的数据。
        :param prefix: 键前缀
        :param exclude: 排除的键前缀
        """
        for key in self.scan_iter(match=f'{prefix}*'):
            if exclude and key.startswith(exclude):
                continue
            self.delete_key(key)
        logger.info(f"Deleted keys with prefix '{prefix}' (exclude: '{exclude}')")


class AppRedis(RedisHandler):
    def __init__(self):
        super().__init__(
            host=settings.redis.host,
            port=settings.redis.port,
            password=settings.redis.password,
            db=settings.redis.database,
            retry_on_failure=True,
            max_retries=3,
        )


# 全局实例化，便于外部调用
redis_handler = AppRedis()

# 示例：如何在自动化框架中调用
if __name__ == "__main__":
    # 模块初始化
    redis_tool = RedisHandler(host="localhost", port=6379)

    # 示例操作
    # try:
    #     redis_tool.set_key("automation_key", "test_value", ttl=3600)
    #     value = redis_tool.get_key("automation_key")
    #     print(f"Key Value: {value}")
    #
    #     keys = redis_tool.get_keys("auto*")
    #     print(f"Matching Keys: {keys}")
    #
    #     redis_tool.delete_key("automation_key")
    # except Exception as e:
    #     print(f"Error: {e}")
